package common.config.mybatisplus;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import common.config.datasource.DataSourceConstants;
import common.config.jwt.JwtUtil;
import common.model.enums.EnvironmentEnum;
import common.util.Constants;
import common.util.HttpRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * MyBatisPlus自定义字段自动填充处理类 - 实体类中使用 @TableField 注解<br/>
 * 3.3.0版本以后推荐使用strictInsertFill()和strictUpdateFill()进行填充，会判断字段是否加了自动填充的注解<br/>
 * setFieldValByName()和fillStrategy()不会判断字段是否加了自动填充的注解，需要自己判断<br/>
 * fillStrategy()不能填充乐观锁字段，因为默认字段有值就不进行填充<br/>
 *
 * @author 米泽鹏
 */
@Component
@Slf4j
public class CustomMetaObjectHandler implements MetaObjectHandler {

	private static final String INSERT_DESC = "插入数据";
	private static final String UPDATE_DESC = "更新数据";
	@Value("${spring.application.name}")
	private String appName;
	@Resource
	private Environment environment;

	/**
	 * 插入数据时
	 */
	@Override
	public void insertFill(MetaObject metaObject) {
		Date current = new Date();
		// 创建时间
		strictInsertFill(metaObject, DataSourceConstants.CREATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
		printLog(INSERT_DESC, DataSourceConstants.CREATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
		// 更新时间
		strictInsertFill(metaObject, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
		printLog(INSERT_DESC, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
		// 操作人
		switch (appName) {
			case Constants.SERVICE_NAME_GATEWAY:
			case Constants.SERVICE_NAME_SYSTEM:
			case Constants.SERVICE_NAME_GAME:
				String operator = JwtUtil.getUsernameFromRequest(HttpRequestUtil.getRequest());
				if (StrUtil.isNotBlank(operator)) {
					strictInsertFill(metaObject, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, String.class, operator);
					printLog(INSERT_DESC, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, operator);
				}
				break;
			case Constants.SERVICE_NAME_LOG:
			default:
				break;
		}
	}

	/**
	 * 更新数据时
	 */
	@Override
	public void updateFill(MetaObject metaObject) {
		Date current = new Date();
		// 更新时间
		strictUpdateFill(metaObject, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
		printLog(UPDATE_DESC, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
		// 操作人
		switch (appName) {
			case Constants.SERVICE_NAME_GATEWAY:
			case Constants.SERVICE_NAME_SYSTEM:
			case Constants.SERVICE_NAME_GAME:
				String operator = JwtUtil.getUsernameFromRequest(HttpRequestUtil.getRequest());
				if (StrUtil.isNotBlank(operator)) {
					strictUpdateFill(metaObject, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, String.class, operator);
					printLog(UPDATE_DESC, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, operator);
				}
				break;
			case Constants.SERVICE_NAME_LOG:
			default:
				break;
		}
	}

	/**
	 * 自定义严格模式填充策略：<br/>
	 * ① 覆盖原值<br/>
	 * ② 如果提供的值为null则不填充<br/>
	 */
	@Override
	public MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
		Object obj = fieldVal.get();
		if (Objects.nonNull(obj)) {
			metaObject.setValue(fieldName, obj);
		}
		return this;
	}

	/**
	 * 打印日志
	 *
	 * @param fillType  插入数据/更新数据
	 * @param fieldName 字段名
	 * @param fieldVal  字段值
	 */
	private void printLog(String fillType, String fieldName, Object fieldVal) {
		// 在非生产环境打印日志
		if (!environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
			log.info("MyBatisPlus自动填充【{}】 - {}:{}", fillType, fieldName, fieldVal);
		}
	}

}
